use std::path::Path;

use bc_base::{
    TransactionId,
    transaction::{Transaction, TransactionIndexNode},
};

use crate::BlockDataStorageError;
use crate::inner_storage::DbHashMap;
use crate::inner_storage::{
    transaction::TransactionStorage, transaction_index::TransactionIndexStorage,
};

#[derive(Debug)]
pub struct TransactionAndIndexStorage {
    tx_storage: TransactionStorage,
    tx_index_storage: TransactionIndexStorage,
}

impl TransactionAndIndexStorage {
    pub fn new(db_root_dir: &Path) -> Result<Self, bc_db::DBError> {
        Ok(Self {
            tx_storage: TransactionStorage::new(db_root_dir)?,
            tx_index_storage: TransactionIndexStorage::new(db_root_dir)?,
        })
    }

    pub async fn load_tx_index_to_cache(
        &self,
        tx_index: &mut DbHashMap<TransactionId, TransactionIndexNode>,
    ) -> Result<(), BlockDataStorageError> {
        *tx_index = self.tx_index_storage.get_all_data().await;
        Ok(())
    }

    #[cfg(debug_assertions)]
    pub async fn get_item_number(&self) -> usize {
        let n1 = self.tx_storage.get_item_number().await;

        #[cfg(debug_assertions)]
        {
            let n2 = self.tx_index_storage.get_item_number().await;
            debug_assert_eq!(n1, n2);
        }

        n1
    }

    pub async fn flush_tx_and_index_db(&self) -> Result<(), BlockDataStorageError> {
        self.tx_storage.flush_database().await?;
        self.tx_index_storage.flush_database().await?;
        Ok(())
    }

    pub async fn write_tx_and_index_to_db(
        &self,
        tx: &Transaction,
    ) -> Result<(), BlockDataStorageError> {
        // 1. tx in db
        let tx_id = tx.calc_tx_id();
        self.tx_storage.put(&tx_id, tx).await?;

        // 2. tx-index in db
        let tx_index_node = TransactionIndexNode::new(tx);
        self.tx_index_storage.put(&tx_id, &tx_index_node).await?;

        Ok(())
    }
}

// for transaction
impl TransactionAndIndexStorage {
    pub async fn tx_exists_in_db(&self, tx_id: &TransactionId) -> bool {
        self.tx_storage.exists(tx_id).await
    }

    pub async fn read_tx_from_db(
        &self,
        tx_id: &TransactionId,
    ) -> Result<Transaction, BlockDataStorageError> {
        let r = self.tx_storage.get(tx_id).await?;
        match r {
            Some(tx) => Ok(tx),
            None => Err(BlockDataStorageError::TransactionNotFound(*tx_id)),
        }
    }

    pub async fn write_tx_to_db(
        &self,
        tx_id: &TransactionId,
        tx: &Transaction,
    ) -> Result<(), BlockDataStorageError> {
        self.tx_storage.put(tx_id, tx).await?;
        Ok(())
    }

    pub async fn delete_tx_from_db(
        &self,
        tx_id: &TransactionId,
    ) -> Result<(), BlockDataStorageError> {
        self.tx_storage.erase(tx_id).await?;
        Ok(())
    }
}

// for transaction index
impl TransactionAndIndexStorage {
    pub async fn tx_index_exists_in_db(&self, tx_id: &TransactionId) -> bool {
        self.tx_index_storage.exists(tx_id).await
    }

    pub async fn read_tx_index_from_db(
        &self,
        tx_id: &TransactionId,
    ) -> Result<TransactionIndexNode, BlockDataStorageError> {
        let r = self.tx_index_storage.get(tx_id).await?;
        match r {
            Some(tx_index) => Ok(tx_index),
            None => Err(BlockDataStorageError::TransactionIndexNotFound(*tx_id)),
        }
    }

    pub async fn write_tx_index_to_db(
        &self,
        tx_id: &TransactionId,
        tx_index_node: &TransactionIndexNode,
    ) -> Result<(), BlockDataStorageError> {
        self.tx_index_storage.put(tx_id, tx_index_node).await?;
        Ok(())
    }

    pub async fn delete_tx_index_from_db(
        &self,
        tx_id: &TransactionId,
    ) -> Result<(), BlockDataStorageError> {
        self.tx_index_storage.erase(tx_id).await?;
        Ok(())
    }
}
